前一篇文章中,我們介紹了 State Flow 以及它的使用方式,本篇將繼續討論 State Flow。
我們知道 SharedFlow 有提供一個函式能夠讓一般的 Flow 轉成 SharedFlow,無獨有偶,State Flow 也有類似的函式,它的名稱你應該不難猜到,它就是 stateIn
。
public fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T>
如同 sharedIn ,需要傳入 CoroutineScope
來確定這個 Flow 執行的 Scope ,以及 SharingStarted
何時才會啟動 Flow。不一樣的是第三個參數 initialValue
,因為 state flow 是包含初始值的,所以用 stateIn 建立時,也必須要帶入。
使用範例:
class Day24 {
val scope = CoroutineScope(Job())
fun stateFlow(): Flow<Int> = flow {
emit(2)
emit(3)
}.stateIn(
scope,
SharingStarted.WhileSubscribed(),
1
)
}
我們利用 stateIn
將 flow 轉成 StateFlow ,不過因為在 flow 當中也有發射一些值,我們看看會是怎麼樣的結果。
fun main() = runBlocking {
val day24 = Day24()
day24.stateFlow().collect { println(it) }
}
1
3
在這邊因為 StateFlow 只會記得最後一個值,所以中間的 2 就被拋棄了。
前面的 stateIn 是讓 Flow 轉成 StateFlow,其中需要帶給他一個初始值,假如 Flow 為空時,最少還是會有一個初始值可供使用。
譬如:
val stateFlow3: Flow<Int> = emptyFlow<Int>().stateIn(
scope,
SharingStarted.Lazily,
1
)
那麼我們取用這個 StateFlow 的時候,就只會出現初始值。(因為StateFlow 是唯獨的)
fun main() = runBlocking{
day24.stateFlow3.collect { println(it) }
}
1
還有另外一種 stateIn,它本身是不需要初始值的,所以它的初始值就必須要從 Flow 來取得,那麼我們知道 Flow 裏面的 FlowCollector
是一個 suspend 函式,也就是說 Flow 在產生數值的時候有可能會花費比較多的時間,所以如果 stateIn 要依賴 Flow 的值時,stateIn 就必須要是 suspend 函式才行。
它的定義如下:
public suspend fun <T> Flow<T>.stateIn(scope: CoroutineScope): StateFlow<T>
可以發現,他只有一個參數: CoroutineScope
,而且它還是一個 suspend 函式。
使用看看:
suspend fun stateFlow2(): Flow<Int> = flow {
delay(1000)
emit(2)
}.stateIn(scope)
我們在這邊延遲一秒鐘模擬產生數值所花費的時間,如果按照上面的介紹,當我們調用 collect 時,StateFlow 在一秒內應該不會有任何的值,當一秒之後 Flow 產生一個值後,collect 才會有值出現。
fun main() = runBlocking {
val day24 = Day24()
day24.stateFlow2().collect { println(it) }
}
public interface StateFlow<out T> : SharedFlow<T> {
/**
* The current value of this state flow.
*/
public val value: T
}
public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
/**
* The current value of this state flow.
*
* Setting a value that is [equal][Any.equals] to the previous one does nothing.
*
* This property is **thread-safe** and can be safely updated from concurrent coroutines without
* external synchronization.
*/
public override var value: T
/**
* Atomically compares the current [value] with [expect] and sets it to [update] if it is equal to [expect].
* The result is `true` if the [value] was set to [update] and `false` otherwise.
*
* This function use a regular comparison using [Any.equals]. If both [expect] and [update] are equal to the
* current [value], this function returns `true`, but it does not actually change the reference that is
* stored in the [value].
*
* This method is **thread-safe** and can be safely invoked from concurrent coroutines without
* external synchronization.
*/
public fun compareAndSet(expect: T, update: T): Boolean
}
先從繼承的項目來看, StateFlow 是繼承 SharedFlow,而 MutableStateFlow 除了繼承 StateFlow 外,還多繼承了 MutableSharedFlow。
所以 StateFlow 是 SharedFlow 的特例,而 MutableSharedFlow 則是能夠與 MutableSharedFlow 一樣能夠呼叫 emit
來更新數值。不過由於 StateFlow 只有包含一個數值,所以呼叫 emit 之後,就會直接更新原本 State 的 value。
而裏面的內容則是 StateFlow 的 value 是 val 的,而 MutableStateFlow 則是 var的,且多了一個 compareAndSet()
。
還記得我們在上一篇中怎麼去建立 StateFlow 以及 MutableStateFlow 的嗎?
private val _state = MutableStateFlow(false)
val state = _state.asStateFlow()
StateFlow.kt 提供了兩個函式,讓我們直接建立 StateFlow 、MutableStateFlow。
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> = ReadonlyStateFlow(this, null)
這邊就不講太多細節的內容了。
StateFlow 是 SharedFlow 的特別用法,它只存一個值,所以適合使用在狀態通知上,所以稱之為 StateFlow。
我們在建立類別時,可以讓更新資料的方法隱藏在類別內,只把讀取的部分暴露給外面使用者,所以我們可以使用 MutableStateFlow 建立一個可以修改的 StateFlow,並且使用 asStateFlow 讓它轉成不可變的 StateFlow 才暴露給外面的人,在對外暴露一個可以更新 MutableStateFlow 的函式即可。
如果想要直接從 Flow 建立 StateFlow ,我們可以使用 stateIn 來將 cold flow 轉成 hot flow,要記得 stateIn 有兩種格式,一種是有初始值的,另一種則是從 Flow 的 emit 取得。
Kotlin Taiwan User Group
Kotlin 讀書會
有興趣的讀者歡迎參考:https://coroutine.kotlin.tips/
天瓏書局